namespace Rsdn.TreeGrid
{
    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Reflection;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;

    /// <summary>
    /// Редактор перечислений с возможностью множественного выбора.
    /// </summary>
    public class FlagsEditor : UITypeEditor
    {
        private IWindowsFormsEditorService _edSvc;
        private CheckedListBox _clb;
        private ToolTip _tooltipControl;
        private bool _handleLostfocus;

        /// <summary>
        /// Overrides the method used to provide basic behaviour for selecting editor.
        /// Shows our custom control for editing the value.
        /// </summary>
        /// <param name="context">The context of the editing control</param>
        /// <param name="provider">A valid service provider</param>
        /// <param name="value">The current value of the object to edit</param>
        /// <returns>The new value of the object</returns>
        public override object EditValue(ITypeDescriptorContext context,
                                         IServiceProvider provider, object value)
        {
            if (context != null
                && context.Instance != null
                && provider != null)
            {
                _edSvc =
                    (IWindowsFormsEditorService)
                    provider.GetService(typeof(IWindowsFormsEditorService));
                if (_edSvc != null)
                {
                    // Create a CheckedListBox and populate it with all the enum values
                    _clb = new CheckedListBox();
                    _clb.BorderStyle = BorderStyle.FixedSingle;
                    _clb.CheckOnClick = true;
                    _clb.MouseDown += OnMouseDown;
                    _clb.MouseMove += OnMouseMoved;
                    _tooltipControl = new ToolTip();
                    _tooltipControl.ShowAlways = true;
                    foreach (
                        string name in Enum.GetNames(context.PropertyDescriptor.PropertyType))
                    {
                        object enumVal = Enum.Parse(context.PropertyDescriptor.PropertyType, name);
                        int intVal = (int)Convert.ChangeType(enumVal, typeof(int));
                        FieldInfo fi = context.PropertyDescriptor.PropertyType.GetField(name);
                        DisplayNameAttribute[] dnAttrs = (DisplayNameAttribute[])
                                                         fi.GetCustomAttributes(typeof(DisplayNameAttribute), false);
                        DescriptionAttribute[] descAttrs =
                            (DescriptionAttribute[])
                            fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
                        string displayName = dnAttrs.Length > 0 ? dnAttrs[0].DisplayName : fi.Name;
                        // Store the the description
                        string tooltip = descAttrs.Length > 0 ? descAttrs[0].Description : string.Empty;
                        // Get the int value of the current enum value (the one being edited)
                        int intEdited = (int)Convert.ChangeType(value, typeof(int));
                        // Creates a clbItem that stores the name, the int value and the tooltip
                        ClbItem item = new ClbItem(displayName, intVal, tooltip);
                        // Get the checkstate from the value being edited
                        bool checkedItem = (intEdited & intVal) > 0;
                        // Add the item with the right check state
                        _clb.Items.Add(item, checkedItem);
                    }
                    // Show our CheckedListbox as a DropDownControl. 
                    // This methods returns only when the dropdowncontrol is closed
                    _edSvc.DropDownControl(_clb);
                    // Get the sum of all checked flags
                    int result = 0;
                    foreach (ClbItem obj in _clb.CheckedItems)
                    {
                        result += obj.Value;
                    }
                    // return the right enum value corresponding to the result
                    return Enum.ToObject(context.PropertyDescriptor.PropertyType, result);
                }
            }
            return value;
        }
        /// <summary>
        /// Shows a dropdown icon in the property editor
        /// </summary>
        /// <param name="context">The context of the editing control</param>
        /// <returns>Returns <c>UITypeEditorEditStyle.DropDown</c></returns>
        public override UITypeEditorEditStyle GetEditStyle(
            ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.DropDown;
        }

        /// <summary>
        /// When got the focus, handle the lost focus event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnMouseDown(object sender, MouseEventArgs e)
        {
            if (!_handleLostfocus &&
                _clb.ClientRectangle.Contains(_clb.PointToClient(new Point(e.X, e.Y))))
            {
                _clb.LostFocus += ValueChanged;
                _handleLostfocus = true;
            }
        }

        /// <summary>
        /// Occurs when the mouse is moved over the checkedlistbox. 
        /// Sets the tooltip of the item under the pointer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnMouseMoved(object sender, MouseEventArgs e)
        {
            int index = _clb.IndexFromPoint(e.X, e.Y);
            if (index >= 0)
            {
                _tooltipControl.SetToolTip(_clb, ((ClbItem)_clb.Items[index]).Tooltip);
            }
        }

        /// <summary>
        /// Close the dropdowncontrol when the user has selected a value
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ValueChanged(object sender, EventArgs e)
        {
            if (this._edSvc != null)
            {
                this._edSvc.CloseDropDown();
            }
        }
        #region ClbItem class

        /// <summary>
        /// Internal class used for storing custom data in listviewitems
        /// </summary>
        internal class ClbItem
        {
            private readonly string _str;
            private readonly string _tooltip;
            private readonly int _value;

            public ClbItem(string str, int value, string tooltip)
            {
                this._str = str;
                this._value = value;
                this._tooltip = tooltip;
            }

            /// <summary>
            /// Gets the int value for this item
            /// </summary>
            public int Value
            {
                get { return _value; }
            }

            /// <summary>
            /// Gets the tooltip for this item
            /// </summary>
            public string Tooltip
            {
                get { return _tooltip; }
            }

            /// <summary>
            /// Gets the name of this item
            /// </summary>
            /// <returns>The name passed in the constructor</returns>
            public override string ToString()
            {
                return _str;
            }
        }
    }
    #endregion
}


